package com.ytktt.study.week3.task1;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;

/**
 * HttpClient4 请求实例
 *
 * @author ytk
 */
public class HttpClient4 {

    private HttpClient4() {}

    private static class HttpClient4Holder {
        private static final HttpClient4 INSTANCE = new HttpClient4();
    }

    public static HttpClient4 getInstance() {
        return HttpClient4Holder.INSTANCE;
    }

    public String get(String url) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.custom()
                // 自定义 keepalive 策略
                .setKeepAliveStrategy(getConnectionKeepAliveStrategy())
                // 添加自定义请求重试处理器
                .setRetryHandler(getHttpRequestRetryHandler())
                // 设置连接管理器
                .setConnectionManager(getPoolingHttpClientConnectionManager())
                .build()) {
            RequestConfig requestConfig = RequestConfig.custom()
                    // 设置 socket 超时时间（请求服务器传输数据的事件）
                    .setSocketTimeout(3_000)
                    // 设置连接超时时间（请求服务器发起建立连接的时间）
                    .setConnectTimeout(3_000)
                    .build();
            HttpGet httpGet = new HttpGet(url);
            httpGet.setConfig(requestConfig);

            String result = "";

            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                StatusLine statusLine = response.getStatusLine();
                if (HttpStatus.SC_OK == statusLine.getStatusCode()) {
                    HttpEntity entity = response.getEntity();
                    result = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                } else {
                    System.out.println("请求发生错误: " + statusLine.getStatusCode());

                }
            }
            return result;
        }

    }

    private static ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
        return new DefaultConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                long keepAlive = super.getKeepAliveDuration(response, context);
                // 如果服务端没有设置keepalive
                if (keepAlive == -1) {
                    // 设置 keepalive时间为3s
                    keepAlive = 3_000L;
                }
                return keepAlive;
            }
        };
    }

    private static HttpRequestRetryHandler getHttpRequestRetryHandler() {
        return (exception, executionCount, context) -> {
            if (executionCount >= 5) {
                // 重试次数不能大于等于5次
                return false;
            }
            if (exception instanceof InterruptedIOException) {
                // IO 中断不再尝试
                return false;
            }
            if (exception instanceof UnknownHostException) {
                // 找不到域名 不再尝试
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                // 连接超时再次尝试
                return true;
            }
            if (exception instanceof SSLException) {
                // SSL 握手失败再次重试
                return true;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = true;
            if (idempotent) {
                // 可以根据request判断请求是否是幂等的，例如get/delete/put方法
                return true;
            }
            return false;
        };
    }

    private static PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
        // 池化管理连接
        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
        // 设置连接池的最大连接数 默认 20
        manager.setMaxTotal(200);
        // 每个请求最多的并发量 默认2
        manager.setDefaultMaxPerRoute(32);
        return manager;
    }

}
